import Taro from "@tarojs/taro";
import React, {
  useReducer,
  useContext,
  createContext,
  useEffect,
  useState,
  useCallback,
} from "react";
import { AuthSteps } from "@/data/authorization/authSteps";

type UserType = null | {
  nickname: string;
  phone: string;
  openID: string;
  unionID: string;
  uid: number;
  token: string;
};

type AuthStateType = {
  user?: UserType;
  authStep?: AuthSteps;
  serverSide?: boolean;
  requireAuth?: boolean;
  authCallback?: Function | null;
};

export type AuthAction =
  | { type: "REQUIRE_AUTH"; data?: Function | null }
  | { type: "UPDATE_AUTH"; data?: AuthStateType }
  | { type: "CALLBACK"; data: Function | null };

type AuthContextType = {
  authState: AuthStateType;
  dispatchAuthAction: React.Dispatch<AuthAction>;
  getAuthStep: Function;
  handleRegister: Function;
  handleBindPhone: Function;
};

const initialState: AuthStateType = {
  user: null,
  authStep: AuthSteps.ONE_SILENT,
  serverSide: false,
  requireAuth: false,
  authCallback: null,
};

const authReducer = (state: AuthStateType, { type, data }: AuthAction) => {
  switch (type) {
    case "REQUIRE_AUTH":
      return {
        ...state,
        requireAuth: true,
      };
    case "UPDATE_AUTH":
      if (data) {
        return {
          ...state,
          ...data,
        };
      } else {
        return state;
      }
    case "CALLBACK":
      return {
        ...state,
        requireAuth: true,
        authStep: AuthSteps.ONE_SILENT,
        serverSide: false,
        authCallback: typeof data === "function" ? data : null,
      };
    default:
      throw new Error(`Unhandled action type: ${type}`);
  }
};

// @ts-ignore
const authContext = createContext<AuthContextType>({
  authState: initialState,
});

function AuthProvider({ children }) {
  let user: UserType = null;
  const [isApiCalling, setApiCalling] = useState(false);
  const [Id, setId] = useState<string>("");
  const [regKey, setRegKey] = useState<string>("");
  const [userInfo, setUserInfo] = useState<UserType>(null);
  const [authState, dispatchAuthAction] = useReducer<
    React.Reducer<AuthStateType, AuthAction>
  >(authReducer, initialState);

  useEffect(() => {
    const header = Taro.getStorageSync("header");
    if (header && header.token && new Date() < new Date(header.expire)) {
      dispatchAuthAction({
        type: "UPDATE_AUTH",
        data: {
          authStep: AuthSteps.FOUR_LOGIN,
          requireAuth: false,
        },
      });
    } else {
      dispatchAuthAction({ type: "REQUIRE_AUTH" });
    }
  }, []);

  const checkSession = () => {
    Taro.checkSession({
      success: function () { },
      fail: function () {
        //getAuthStep({});
      },
    });
  };

  // Note: can get rid off multiple login using debounce, but may introduce other issues
  // Would use a throttle instead of debounce -> otherwise basically delaying the call
  const getAuthStep = useCallback(
    (data) => {
      if (authState.requireAuth && !isApiCalling) {
        setApiCalling(true);
        Taro.showLoading();
        Taro.login({
          success(res: any) {
            if (res.code) {
              Taro.request({
                url: `${process.env.apiEndpoint}/wechat/authorize/program/login?code=${res.code}`,
                method: "POST",
                data: data,
                success: function (e: any) {
                  Taro.hideLoading();
                  const { data } = e;
                  if (data.data && data.data.type == 1) {
                    Taro.setStorage({ key: "header", data: null });
                    setApiCalling(false);
                    updateAuth(dispatchAuthAction, {
                      serverSide: true,
                      authStep: AuthSteps.ONE_SILENT,
                      requireAuth: true,
                    });
                  } else if (data.data && data.data.type == 2) {
                    Taro.setStorage({ key: "header", data: null });
                    setApiCalling(false);
                    setId(data.data.unregisteredUniqueID);
                    updateAuth(dispatchAuthAction, {
                      serverSide: true,
                      authStep: AuthSteps.TWO_OID,
                      requireAuth: true,
                    });
                  } else if (data.data && data.data.type == 3) {
                    Taro.setStorage({ key: "header", data: null });
                    setApiCalling(false);
                    !Id && setId(data.data.unregisteredUniqueID);
                    setRegKey(data.data.key);
                    updateAuth(dispatchAuthAction, {
                      serverSide: true,
                      authStep: AuthSteps.THREE_UNAME,
                      requireAuth: true,
                    });
                  } else if (data.data && data.data.type == 4) {
                    handleAuthData(e);
                  }
                },
                fail: function () {
                  Taro.hideLoading();
                  checkSession();
                },
              });
            }
          },
        });
      }
    },
    [authState.requireAuth]
  );

  const handleRegister = (nickName: string) => {
    !isApiCalling && getAuthStep({ nickName: nickName });
  };

  const handleBindPhone = (e: any) => {
    if (!isApiCalling) {
      setApiCalling(true);
      Taro.showLoading();
      Taro.login({
        success(res: any) {
          if (res.code) {
            Taro.request({
              url: `${process.env.apiEndpoint}/wechat/register/binding/phone`,
              method: "POST",
              data: {
                code: res.code,
                encryptedData: e.detail.encryptedData,
                iv: e.detail.iv,
                key: regKey,
                type: "routine",
              },
              success: function (e: any) {
                Taro.hideLoading();
                handleAuthData(e);
              },
              fail: function () {
                setApiCalling(true);
                Taro.hideLoading();
                checkSession();
              },
            });
          }
        },
      });
    }
  };

  const handleAuthData = async (e: any) => {
    setApiCalling(false);
    const { data } = e;
    if (data.data && data.data.type == 4) {
      user = {
        nickname: data.data.nickName,
        phone: data.data.phone,
        openID: data.data.openID,
        unionID: data.data.unionID,
        token: data.data.token,
        uid: data.data.uid,
      };
      //tracking 
      const { uid, openID, unionID } = data.data;
      Taro.getApp().hxt.identify({
        openid: openID,
        unionid: unionID,
      });
      Taro.getApp().hxt.setUserInfo({
        cust_id: uid,
        loigin_type: "client",
      });

      setUserInfo(user);
      const date = new Date();
      const timezoneOffset = date.getTimezoneOffset() * 60000;
      const timezoneDate = new Date(
        date.getTime() - timezoneOffset + 24 * 60 * 60 * 1000
      );
      Taro.setStorage({
        key: "header",
        data: {
          token: user.token,
          expire: new Date(timezoneDate).toISOString(),
        },
      });
      updateAuth(dispatchAuthAction, {
        user: user,
        serverSide: true,
        authStep: AuthSteps.FOUR_LOGIN,
        requireAuth: false,
      });
      typeof authState.authCallback === "function" && authState.authCallback();
    }
  };

  const value: AuthContextType = {
    authState,
    dispatchAuthAction,
    getAuthStep,
    handleRegister,
    handleBindPhone,
  };

  return <authContext.Provider value={value}>{children}</authContext.Provider>;
}

const useAuth = () => {
  return useContext(authContext);
};

const setAuth = async (
  dispatch: React.Dispatch<AuthAction>,
  callback?: Function | null
) => {
  dispatch({ type: "REQUIRE_AUTH", data: callback || null });
  return true;
};

const updateAuth = (
  dispatch: React.Dispatch<AuthAction>,
  data: AuthStateType
) => {
  dispatch({ type: "UPDATE_AUTH", data: data });
  return true;
};

const requestAuth = async (
  dispatch: React.Dispatch<AuthAction>,
  callback?: Function
) => {
  Taro.getStorage({
    key: "header",
    success: async function (res) {
      if (
        !res.data ||
        !res.data.token ||
        new Date() > new Date(res.data.expire)
      ) {
        dispatch && setAuth(dispatch, callback);
      } else {
        callback && callback();
      }
    },
    fail: async function () {
      dispatch && setAuth(dispatch, callback);
    },
  });
};

const apiWithAuth = async (
  endpoint: string,
  callback: Function,
  misc?: {
    query?: any;
    method?: "GET" | "POST";
    authDispatch?: React.Dispatch<AuthAction>;
    withoutLoading?: boolean;
  }
) => {
  try {
    !misc?.withoutLoading && Taro.showLoading();

    const header = Taro.getStorageSync("header");

    const token =
      header && header.token && new Date() < new Date(header.expire)
        ? header.token
        : "";
    let response = await Taro.request({
      url: `${process.env.apiEndpoint}${endpoint}`,
      method: misc && misc.method ? misc.method : "GET",
      data: misc && misc.query,
      header: { token: token },
    });

    !misc?.withoutLoading && Taro.hideLoading();
    if (response.data.code === 401) {
      //Taro.setStorage({ key: "header", data: null });
      return new Promise<void>((resolve) => {
        if (
          misc &&
          misc.authDispatch &&
          typeof misc.authDispatch === "function"
        ) {
          misc.authDispatch({
            type: "CALLBACK",
            data: () => resolve(apiWithAuth(endpoint, callback, misc)),
          });
        } else {
          resolve();
        }
      });
    }
    misc &&
      misc.authDispatch &&
      typeof misc.authDispatch === "function" &&
      misc.authDispatch({
        type: "UPDATE_AUTH",
        data: { requireAuth: false, authCallback: null },
      });
    return callback && callback(response);
  } catch (error) {
    console.error(error);
    // Handle the error
  }
};

export {
  AuthContextType,
  AuthProvider,
  authContext,
  useAuth, //use the context globally
  setAuth, //dispatcher setRequire true, callback is optional
  updateAuth, //dispatcher requireLogin or not
  apiWithAuth, //call api with authorization required (optional)
  requestAuth, //requestLogin with a re login callback !!useless!!
};
